package ferp.core.game;

import ferp.core.card.Card;
import ferp.core.player.Hand;
import ferp.core.player.Player;

/**
 * User: igorgok
 * Date: 5/19/11 4:00 PM
 */
public class Trick
{
  // 32-bit value {
  //   card count:2 talon card:6, 
  //   1st player id:2, 1st card:6, 
  //   2nd player id:2, 2nd card:6, 
  //   3rd player id:2, 3rd card:6
  // }

  // get trick size
  public static int size(int hash) {return hash >>> 30;}

  // add talon card
  public static int add(int hash, byte card) {return hash | card << 24;}

  // add player card
  public static int add(int hash, int player, byte card)
  {
    int shift = shifts[size(hash)];

    return increment(hash) | ((int)card) << shift | player << (shift + 6);
  }

  // remove last card
  public static int pop(int hash) {return decrement(hash & ~(0xFF << shifts[size(hash) - 1]));}

  // reset trick cards, leaving talon card intact
  public static int reset(int hash) {return hash & 0x3F000000;}

  // shortcut to get talon card
  public static Card talon(int hash) {return card(hash, 3);}

  // get byte containing compressed card and player id at index
  public static byte get(int hash, int index) {return (byte)((hash >>> shifts[index]) & 0xFF);}

  // get byte containing card index only at index
  public static Card card(int hash, int index) {return Card.card((byte)((hash >>> shifts[index]) & CARD));}

  // determine whose card is at given index
  public static byte owner(int hash, int index) {return owner((byte)((hash >>> shifts[index]) & 0xFF));}

  // get card of a specific player
  public static Card card(int hash, Player p)
  {
    int size = size(hash);

    for (int i = 0; i < size; ++i)
      if (owner(hash, i) == p.id())
        return card(hash, i);

    return null;
  }

  // get trick suit (either talon card suit or first card suit)
  public static Card.Suit suit(int hash)
  {
    Card talon = talon(hash);

    if (talon != null)
      return talon.suit;

    Card first = card(hash, 0);

    return first == null ? null : first.suit;
  }

  // get the highest card in the trick according to the rules
  public static Card highest(int hash, Card.Suit trump)
  {
    Card talon = talon(hash);
    Card c1 = Trick.card(hash, 0), c2 = Trick.card(hash, 1), c3 = Trick.card(hash, 2);

    // are we in the passing mode?
    if (talon != null)
    {
      Card.Suit primary = talon.suit;

      if (c3 != null)
        return highest(highest(c1, c2, primary), c3, primary);

      if (c2 != null)
        return c1.suit == primary || c2.suit == primary ? highest(c1, c2, primary) : null;

      return c1 != null && c1.suit == primary ? c1 : null;
    }

    if (c3 != null)
      return highest(highest(c1, c2, trump), c3, trump);

    if (c2 != null)
      return highest(c1, c2, trump);

    return c1;
  }
  
  // get trick winner
  public static byte winner(int hash, Card.Suit trump)
  {
    Card c = Trick.highest(hash, trump);

    if (c == card(hash, 0))
      return Trick.owner(hash, 0);
    if (c == card(hash, 1))
      return Trick.owner(hash, 1);
    if (c == card(hash, 2))
      return Trick.owner(hash, 2);

    return -1;
  }

  // trash trick cards (done after every "playing" round)
  public static int trash(int trick, int trash)
  {
    Card talon = Trick.talon(trick), c1 = Trick.card(trick, 0), c2 = Trick.card(trick, 1), c3 = Trick.card(trick, 2);

    // talon card is not always there
    if (talon != null)
      trash = Hand.add(trash, talon);

    trash = Hand.add(trash, c1);
    trash = Hand.add(trash, c2);
    trash = Hand.add(trash, c3);

    return trash;
  }

  public static String dump(Game g, int hash) {return dump(hash, g.trump());}
  public static String dump(int hash, Card.Suit trump)
  {
    StringBuilder sb = new StringBuilder(128);
    Card talon = Trick.talon(hash);
    byte c1 = Trick.get(hash, 0), c2 = Trick.get(hash, 1), c3 = Trick.get(hash, 2);
    
    if (talon != null)
      sb.append("(").append(talon).append(") ");

    dump(sb, c1);
    dump(sb, c2);
    dump(sb, c3);
    
    if (c1 != 0 && c2 != 0 && c3 != 0)
      sb.append("w=").append(winner(hash, trump));

    return sb.toString();
  }
  
  private static void dump(StringBuilder sb, byte compressed)
  {
    if (compressed != 0)
      sb.append(Trick.owner(compressed)).append(':').append(Trick.card(compressed)).append(' ');
    else
      sb.append("- ");
  }

  private static Card highest(Card c1, Card c2, Card.Suit trump)
  {
    if (c1.suit == c2.suit)
      // do not compare the reserved bits
      return (c1.rank.ordinal() > c2.rank.ordinal() ? c1 : c2);

    return c2.suit == trump ? c2 : c1;
  }

  // get byte containing card index only at index
  private static Card card(byte encoded) {return Card.card((byte)(encoded & CARD));}
  // determine whose card is in the encoded value
  private static byte owner(byte encoded) {return (byte)((encoded >>> 6) & 0x03);}

  // masks
  private static final int CARD   = 0x3F;
  private static final int PLAYER = 0x30;

  // shift values for card order
  private static final int[] shifts = {16, 8, 0, 24};

  // change trick size
  private static int increment(int hash) {return hash + (1 << 30);}
  private static int decrement(int hash) {return hash - (1 << 30);}

/*
  public final int hash() {return hash;}
  public final int size() {return size(hash);}

  // add player card to the trick
  public void add(Card card, Player player) {add(card, player.id());}
  public void add(Card card, int player) {hash = add(hash, player, card.compressed);}
  // add a talon card to the trick
  public void add(Card card) {hash = add(hash, card.compressed);}

  // reset object
  public void reset() {hash = 0;}

  // get talon card
  public Card talon() {return talon(hash);}

  // get trick suit, that is the suit of the card which started the trick
  public Card.Suit suit() {return suit(hash);}

  // determine player with the greatest card value
  //public Player winner(Game g) {return g.player(winner(g.trump()));}
  public int winner(Card.Suit trump) {return winner(hash, trump);}

  @Override
  public String toString() {return dump(Game.ferp(), hash);}

  // trick data
  private int hash;
*/

/*
  public static void main(String[] args)
  {
    Card[] cards = new Card[4];

    for (int i = 0; true; ++i)
    {
      Card.Suit trump = Card.Suit.rising[random.nextInt(Card.Suit.rising.length)];

      generate(cards);

      int trick = cards[3] == null ? 0 : Trick.add(0, cards[3].compressed);

      for (int n = 0; n < cards.length - 1; ++n)
        trick = Trick.add(trick, n, cards[n].compressed);

      System.out.println(Trick.dump(trick, trump));
    }
  }

  private static final Random random = new Random();

  private static void generate(Card[] cards)
  {
    // talon
    cards[cards.length - 1] = random.nextFloat() < 0.5f ? null : Card.card((byte)random.nextInt(Deck.SIZE));

    int used = cards[cards.length - 1] == null ? 0 : cards[cards.length - 1].hash;

    //System.out.print("Cards: (" + cards[cards.length - 1] + ") ");

    for (int n = 0; n < cards.length - 1; ++n)
    {
      Card c = Card.card((byte)random.nextInt(Deck.SIZE));

      while (c == null || (c.hash & used) != 0)
        c = Card.card((byte)random.nextInt(Deck.SIZE));

      used |= c.hash;
      cards[n] = c;

      System.out.print(c + " ");
    }

    System.out.println();
  }
*/
}
